package com.hzw.code.security.exception;


import com.hzw.code.common.utils.ActionException;
import com.hzw.code.common.utils.ActionResult;
import com.hzw.code.common.utils.ResultCodeEnum;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.nio.file.AccessDeniedException;
import java.util.List;

/**
 * 全局异常处理
 *
 * @author: 胡汉三
 * @date: 2020/5/26 12:00
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    private ActionResult commonHandleException(Exception e){
        if(e instanceof ActionException) {
            Object result = ((ActionException) e).getResult();
            if (result instanceof ActionResult) {
                ActionResult actionResult =(ActionResult) result;
                return commonHandleException(actionResult.getCode(), actionResult.getMessage());
            } else {
                return commonHandleException(ResultCodeEnum.ERROR_DEFAULT, getRealExceptionMessage(e));
            }
        }else{
            return commonHandleException(ResultCodeEnum.ERROR_DEFAULT, getRealExceptionMessage(e));
        }
    }

    private ActionResult commonHandleException(ResultCodeEnum resultCode, String errorMsg){
        log.error(errorMsg);
        return commonHandleException(resultCode.getValue(), errorMsg);
    }

    private ActionResult commonHandleException(Integer resultCode, String errorMsg) {
        return new ActionResult(resultCode, errorMsg, null);
    }

    /**
     * 循环exception的cause，获取真正的错误信息
     * @param exception
     * @return
     */
    public String getRealExceptionMessage(Exception exception){
        // 初始化为顶级异常消息
        String message = exception.getMessage();
        if(StringUtils.isBlank(message)
                || message.indexOf("Exception") == -1){
            message = exception.toString();
        }

        Throwable e = exception.getCause();
        // 防止进入过多循环，最多只循环10层异常
        for(int i = 0;i < 10;i++){
            if(e == null){
                break;
            }
            message = e.getMessage();
            if(StringUtils.isBlank(message)
                    || message.indexOf("Exception") == -1){
                message = e.toString();
            }
            e = e.getCause();
        }
        return message;
    }

    @ExceptionHandler(ActionException.class)
    public ActionResult<Object> handleActionExecption(ActionException exception) {
        return commonHandleException(exception);
    }

    @ExceptionHandler(AccessDeniedException.class)
    public ActionResult<Object> accessDeniedException(AccessDeniedException accessDeniedException){
        return commonHandleException(accessDeniedException);
    }

    /**
     *  校验错误拦截处理
     *
     * @param exception 错误信息集合
     * @return 错误信息
     */
    @ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})
    public ActionResult<Object> validationBodyException(Exception exception){
        BindingResult bindResult = null;
        if (exception instanceof BindException) {
            bindResult = ((BindException) exception).getBindingResult();
        } else if (exception instanceof MethodArgumentNotValidException) {
            bindResult = ((MethodArgumentNotValidException) exception).getBindingResult();
        }
        StringBuffer sb = new StringBuffer();
        if (bindResult.hasErrors()) {
            List<ObjectError> errors = bindResult.getAllErrors();
            if(errors != null && errors.size() > 0){
                errors.forEach(p ->{
                    FieldError fieldError = (FieldError) p;
                    log.error("数据校验失败 : object{"+fieldError.getObjectName()+"},field{"+fieldError.getField()+
                            "},errorMessage{"+fieldError.getDefaultMessage()+"}");
                    if(sb.toString().indexOf(fieldError.getDefaultMessage()) == -1){
                        sb.append(fieldError.getDefaultMessage() + ",");
                    }
                });
            }
        }
        String msg = sb.toString();
        if(msg.length() > 1){
            msg = msg.substring(0, msg.length()-1);
        }
        return commonHandleException(ResultCodeEnum.DATA_INPUT_ERROR, msg);
    }

    /**
     * 参数类型转换异常处理
     * @param exception
     * @return
     */
    @ExceptionHandler(HttpMessageConversionException.class)
    public ActionResult<Object> parameterTypeException(HttpMessageConversionException exception){
        return commonHandleException(ResultCodeEnum.DATA_INPUT_TYPE_ERROR, getRealExceptionMessage(exception));
    }

    /**
     * Controller入参格式转换错误异常处理
     * @param exception
     * @return
     */
    @ExceptionHandler({ HttpMessageNotReadableException.class })
    public ActionResult<Object> handleHttpMessageNotReadableException(HttpMessageNotReadableException exception) {
        return commonHandleException(ResultCodeEnum.DATA_INPUT_ERROR, getRealExceptionMessage(exception));
    }

    /**
     * 请求方法不支持异常处理
     * @param exception
     * @return
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public ActionResult<Object> handleMethodNotSupportedException(Exception exception) {
        return commonHandleException(ResultCodeEnum.ERROR_HTTP_METHOD, getRealExceptionMessage(exception));
    }

    /**
     * 类型转换错误
     * @param exception
     * @return
     */
    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    public ActionResult<Object> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception) {
        return commonHandleException(ResultCodeEnum.DATA_INPUT_TYPE_ERROR, getRealExceptionMessage(exception));
    }

    /**
     * 全局程序异常处理类
     * @param exception
     * @return
     */
    @ExceptionHandler({ Exception.class, RuntimeException.class })
    public ActionResult<Object> handleException(Exception exception) {
        exception.printStackTrace();
        return commonHandleException(exception);
    }

    /**
     * jwt异常处理类
     * @param exception
     * @return
     */
    @ExceptionHandler({ ExpiredJwtException.class })
    public ActionResult<Object> handleExpiredJwtException(ExpiredJwtException exception) {
        exception.printStackTrace();
        return commonHandleException(exception);
    }

}
